# Copyright (c) 2007, Enrico Franchi
# All rights reserved.
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions are met:
#
#     * Redistributions of source code must retain the above copyright
#       notice, this list of conditions and the following disclaimer.
#     * Redistributions in binary form must reproduce the above copyright
#       notice, this list of conditions and the following disclaimer in the
#       documentation and/or other materials provided with the distribution.
#     * Neither the name of the University of California, Berkeley nor the
#       names of its contributors may be used to endorse or promote products
#       derived from this software without specific prior written permission.
#
# THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND ANY
# EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
# DISCLAIMED. IN NO EVENT SHALL THE REGENTS AND CONTRIBUTORS BE LIABLE FOR ANY
# DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
# (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
# LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
# ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

import itertools
import threading

import port_command
import task_executor
import package
import port_parser
import linguistics
import notifications
from port_command import command, targets
from decorators import synchronized, with_autorelease_pool


import Foundation
from Foundation import NSNotificationCenter, NSNotification, NSObject, NSAutoreleasePool

class RefreshListPorts(object):
    def __init__(self, model, on_complete):
        self.model        = model
        self.on_complete  = on_complete
        # We consider a writing action something that *modifies* the ports
        # here we only read.
        # self.write_action = True
        self.name         = notifications.refreshModelNotificationNameByObject(model)
    
    @with_autorelease_pool
    def __call__(self):
        self.model.refresh()
        self.on_complete()

class ListPortsModel(NSObject):
    def initWithPath_(self, path):
        self.full_data = []
        self.data = self.full_data
        self.lock = threading.RLock()
        self.c = command.PortCommand(path, catcher=self)
        self._issync = True
        self.setFilter(None)
        return self
        
    def run(self):
        self.refresh()
        
    def refresh(self):
        """Actually calls the command to obtain the list of available ports."""
        pass
        
    def notifyRefreshed(self):
        notificationCenter = NSNotificationCenter.defaultCenter()
        notificationCenter.postNotificationName_object_(
            'refreshed_' + linguistics.classNameFromObject(self), self
        ) 
    
    
    def refresh_(self, notification):
        action = RefreshListPorts(self, self.notifyRefreshed)
        te = task_executor.TaskExecutor.sharedExecutor()
        te.addTask(action)
    
    @synchronized
    def setFilter(self, l_filter):
        self._issync = True
        if l_filter is None:
            self.data = self.full_data
        else:
            self.filter = l_filter
            self.data = filter(l_filter, self.full_data)
    
    def syncData(self):
        self.setFilter(self.filter)
        
    def __getitem__(self, key):
        """If the input is a tuple or a list like (i, j) returns
        the j-th element of the i-th column.
        If the input is a string returns a tuple whose first value is the index
        or the row that has as a first element the string and the row itself.
        Otherwise it returns the i-th row
        """
        if not self._issync:
            self.syncData()
        if isinstance(key, (list, tuple)):
            return self.data[key[0]][key[1]]
        elif isinstance(key, (str, unicode)):
            for index, row in itertools.izip(itertools.count(), self.data):
                if row[0] == key:
                    return index, row
        else:
            return self.data[key]
            
    def __len__(self):
        return len(self.data)
        
class AvailablePortsModel(ListPortsModel):
    def initWithPath_(self, path):
        super(AvailablePortsModel, self).initWithPath_(path)
        return self

    def add(self, line):
        elements = line.split()
        if len(elements) == 3:
            self.full_data.append((elements[0], elements[1][1:], elements[2]))
        if self._issync and id(self.data) != id(self.full_data):
            selt._issync = False
    
    @synchronized
    def refresh(self):
        """Actually calls the command to obtain the list of available ports."""
        self.c.list()
        
class InstalledPortsModel(ListPortsModel):
    def initWithPath_(self, path):
        super(InstalledPortsModel, self).initWithPath_(path)
        return self

    def add(self, line):
        elements = line.split()
        if len(elements) == 3:
            self.full_data.append((elements[0], elements[1][1:], True))
        elif len(elements) == 2:
            self.full_data.append((elements[0], elements[1][1:], False))
        if self._issync and id(self.data) != id(self.full_data):
            selt._issync = False
    
    @synchronized
    def refresh(self):
        self.c.installed()
        
class OutdatedPortsModel(ListPortsModel):
    def initWithPath_(self, path):
        super(OutdatedPortsModel, self).initWithPath_(path)
        return self
    
    @synchronized
    def refresh(self):
        self.c.outdated()

    def add(self, line):
        elements = line.split()
        if len(elements) == 4:
            self.full_data.append((elements[0], elements[1], elements[3]))
        if self._issync and id(self.data) != id(self.full_data):
            selt._issync = False
           
class PortModelFactory(object):
    cache = {}
    lock = threading.RLock()
    @classmethod
    def createPortModel(cls, path, name):
        try:
            return PortModelFactory.cache[name]
        except KeyError:
            port = PortModel(path, name)
            PortModelFactory.lock.acquire()
            PortModelFactory.cache[name] = port
            PortModelFactory.lock.release()
            return port
           
class PortModel(package.Package):
    def __init__(self, path, name):
        # We do this sinchronously since it is a very
        # fast operation.
        self.c = port_command.PortCommand(path)
        self.c.info(name)
        s = self.c.getAllData()
        d = port_parser.parse_info(s.split('\n'))
        super(PortModel, self).__init__(d)
        
            
if __name__ == '__main__':
    ap = AvailablePortsModel.alloc().initWithPath_('/opt/local/bin/port')
    ap.run()
    for line in ap:
        print line
    #    
    #ip = InstalledPortsModel('/opt/local/bin/port')
    #for line in ip:
    #    print line
    #    
    #op = OutdatedPortsModel('/opt/local/bin/port')
    #for line in op:
    #    print line
        
    port = PortModel('/opt/local/bin/port', 'python25')
    print port